﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using ProLinq.Projection.Configuration;
using ProLinq.Tracing;

namespace ProLinq.Projection.Visitors.Optimization
{
	internal class FilterReducerVisitor : ExpressionVisitor
	{
		public FilterReducerVisitor()
		{
			this.Params = new List<ParameterExpression>();
		}

		public IEnumerable<Configuration.Projection> Projections { get; set; }
		protected List<ParameterExpression> Params { get; private set; }

		protected override Expression VisitLambda<T>(Expression<T> node)
		{
			var args = node.Parameters.Select(p => this.Visit(p)).Cast<ParameterExpression>().ToArray();
			var body = this.Visit(node.Body);
			return Expression.Lambda(body, args);
		}

		protected override Expression VisitParameter(ParameterExpression node)
		{
			Configuration.Projection projection = this.Projections.FirstOrDefault(p => p.DestinationType == node.Type);
			if (projection != null)
			{
				ParameterExpression param = this.Params.FirstOrDefault(p => p.Type == projection.SourceType);
				if (param != null)
					return param;

				param = Expression.Parameter(projection.SourceType, node.Name);
				this.Params.Add(param);
				return param;
			}

			return base.VisitParameter(node);
		}

		protected override Expression VisitMember(MemberExpression node)
		{
			Configuration.Projection projection = this.Projections.FirstOrDefault(p => p.DestinationType == node.Expression.Type);
			if (projection != null)
			{
				PropertyMap propMap = projection.Map.Where(p => p.DestinationProperty == node.Member).FirstOrDefault();
				if (propMap != null)
				{
					Expression modifiedParameter = this.Visit(node.Expression);
					return Expression.MakeMemberAccess(modifiedParameter, propMap.SourceProperty);
				}
				return null;
			}
			return base.VisitMember(node);
		}

		protected override Expression VisitBinary(BinaryExpression node)
		{
			Expression left = null;
			Expression right = null;

			try
			{
				left = this.Visit(node.Left);
			}
			catch (Exception exc)
			{
				TraceSources.Projection.TraceEvent(TraceEventType.Verbose, 0, "Reduced binary expression left part due to an error.");
				TraceSources.Projection.TraceData(TraceEventType.Verbose, 0, node.Left);
				TraceSources.Projection.TraceData(TraceEventType.Verbose, 0, exc);
			}

			try
			{
				right = this.Visit(node.Right);
			}
			catch (Exception exc)
			{
				TraceSources.Projection.TraceEvent(TraceEventType.Verbose, 0, "Reduced binary expression right part due to an error.");
				TraceSources.Projection.TraceData(TraceEventType.Verbose, 0, node.Right);
				TraceSources.Projection.TraceData(TraceEventType.Verbose, 0, exc);
			}

			if (left == null || right == null)
			{
				if (node.NodeType == ExpressionType.OrElse)
				{
					return null;
				}
				if (node.NodeType == ExpressionType.AndAlso)
				{
					return left ?? right;
				}
			}

			return base.VisitBinary(node);
		}
	}
}
